17 context上下文
前面的文章中,我们学了工具、记忆、中间件、Runtime等等。现在把这些东西串起来,聊一个更根本的问题:怎么让Agent更可靠?
Agent失败的原因通常有两种:
- 模型本身能力不够
- 没有给模型正确的上下文
实际项目中,第二种原因占了大多数。模型其实挺聪明的,但如果你没给它正确的信息、正确的工具、正确的提示词,它就会做出错误的决定。
上下文工程就是解决这个问题的——以正确的格式,在正确的时机,给Agent正确的信息和工具。这是构建可靠Agent最重要的工作。
一、三种上下文类型
Agent执行过程中,你可以控制三类上下文:
| 类型 | 控制什么 | 生命周期 |
|---|---|---|
| 模型上下文 | 系统提示词、消息、工具、模型、输出格式 | 瞬态(每次调用独立) |
| 工具上下文 | 工具能读写什么数据 | 持久(写入后持久保存) |
| 生命周期上下文 | 模型调用和工具调用之间发生什么 | 持久(影响后续步骤) |
二、三种数据来源
这三类上下文都可以从三个数据来源获取信息:
| 数据来源 | 作用域 | 举例 |
|---|---|---|
| Runtime Context | 单次调用 | 用户ID、角色、API密钥 |
| State | 单次对话 | 当前消息列表、上传的文件 |
| Store | 跨对话 | 用户偏好、历史记忆 |
三、模型上下文
模型上下文控制每次LLM调用时它能看到什么。这是影响Agent决策质量最直接的因素。
3.1 系统提示词
系统提示词是给LLM的"人设"和"工作指南"。不同用户、不同场景需要不同的提示词。
根据用户角色动态生成:
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable
@dataclass
class Context:
user_role: str
user_name: str
@wrap_model_call
def dynamic_system_prompt(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
user_role = request.runtime.context.user_role
user_name = request.runtime.context.user_name
prompts = {
"admin": f"你是管理员助手,正在和管理员 {user_name} 对话。你可以执行所有操作。",
"user": f"你是用户助手,正在和 {user_name} 对话。你只能查询信息。",
"guest": "你是访客助手,只能查看公开信息。",
}
system_prompt = prompts.get(user_role, prompts["guest"])
# 根据对话长度调整
if len(request.messages) > 10:
system_prompt += "\n对话已经很长了,请尽量简洁回答。"
return handler(request.override(system_prompt=system_prompt))
agent = create_agent(
model="deepseek-v4-flash",
tools=[...],
middleware=[dynamic_system_prompt],
context_schema=Context,
)3.2 消息
消息是LLM看到的对话历史。你可以往消息里注入额外的上下文信息。
注入用户上传的文件信息:
@wrap_model_call
def inject_file_context(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
uploaded_files = request.state.get("uploaded_files", [])
if uploaded_files:
file_info = "\n".join(
f"- {f['name']} ({f['type']}): {f['summary']}"
for f in uploaded_files
)
context_message = {
"role": "user",
"content": f"当前可用的文件:\n{file_info}\n\n回答问题时可以参考这些文件。"
}
messages = [*request.messages, context_message]
return handler(request.override(messages=messages))
return handler(request)注入用户的写作风格偏好(从长期记忆中读取):
@wrap_model_call
def inject_writing_style(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
user_id = request.runtime.context.user_id
store = request.runtime.store
if store:
style = store.get(("writing_style",), user_id)
if style:
style_guide = (
f"写作风格要求:\n"
f"- 语气: {style.value.get('tone', '专业')}\n"
f"- 称呼: {style.value.get('greeting', '你好')}\n"
f"- 署名: {style.value.get('sign_off', '祝好')}"
)
messages = [*request.messages, {"role": "user", "content": style_guide}]
return handler(request.override(messages=messages))
return handler(request)3.3 工具
工具太多会让LLM选不好,工具太少又限制了能力。根据场景动态选择工具:
@wrap_model_call
def select_tools_by_context(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
user_role = request.runtime.context.user_role
if user_role == "admin":
pass # 管理员可以用所有工具
elif user_role == "editor":
# 编辑不能删除
tools = [t for t in request.tools if t.name != "delete_data"]
return handler(request.override(tools=tools))
else:
# 访客只能用查询工具
tools = [t for t in request.tools if t.name.startswith("query_")]
return handler(request.override(tools=tools))
return handler(request)根据对话阶段选择工具:
@wrap_model_call
def evolve_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
is_authenticated = request.state.get("authenticated", False)
message_count = len(request.messages)
tools = request.tools
if not is_authenticated:
# 未认证只能用公开工具
tools = [t for t in tools if t.name.startswith("public_")]
elif message_count < 5:
# 对话初期限制高级工具
tools = [t for t in tools if t.name != "advanced_search"]
return handler(request.override(tools=tools))3.4 模型
不同场景可以用不同模型,平衡成本和效果:
from langchain.chat_models import init_chat_model
# 预初始化模型
large_model = init_chat_model("deepseek-v3")
standard_model = init_chat_model("deepseek-v4-flash")
budget_model = init_chat_model("deepseek-v4-flash")
@wrap_model_call
def dynamic_model(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
message_count = len(request.messages)
if message_count > 20:
model = large_model # 长对话用大模型
elif message_count > 10:
model = standard_model # 中等对话用标准模型
else:
model = budget_model # 短对话用经济模型
return handler(request.override(model=model))3.5 输出格式
根据场景动态选择输出格式:
from pydantic import BaseModel, Field
class SimpleResponse(BaseModel):
"""简单回复"""
answer: str = Field(description="简短回答")
class DetailedResponse(BaseModel):
"""详细回复"""
answer: str = Field(description="详细回答")
reasoning: str = Field(description="推理过程")
confidence: float = Field(description="置信度 0-1")
@wrap_model_call
def dynamic_output_format(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
user_role = request.runtime.context.user_role
if user_role == "admin":
# 管理员看到详细信息
return handler(request.override(response_format=DetailedResponse))
else:
# 普通用户看到简洁信息
return handler(request.override(response_format=SimpleResponse))四、工具上下文
工具不仅能返回结果给LLM,还能读写各种数据源。
4.1 读取上下文
工具可以通过ToolRuntime读取Runtime Context、State和Store:
from langchain.tools import tool, ToolRuntime
@tool
def get_user_orders(runtime: ToolRuntime[Context]) -> str:
"""获取当前用户的订单"""
# 读取Runtime Context
user_id = runtime.context.user_id
# 读取State
is_authenticated = runtime.state.get("authenticated", False)
if not is_authenticated:
return "请先登录"
# 读取Store
if runtime.store:
prefs = runtime.store.get(("preferences",), user_id)
if prefs:
# 根据用户偏好调整查询
pass
return f"用户 {user_id} 的订单: [...]"4.2 写入上下文
工具可以通过Command更新State,通过store写入长期记忆:
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command
@tool
def authenticate(password: str, runtime: ToolRuntime) -> Command:
"""验证用户密码"""
if password == "correct":
# 写入State:标记为已认证
return Command(update={"authenticated": True})
return Command(update={"authenticated": False})
@tool
def save_preference(key: str, value: str, runtime: ToolRuntime) -> str:
"""保存用户偏好到长期记忆"""
user_id = runtime.context.user_id
if runtime.store:
runtime.store.put(("preferences",), user_id, {key: value})
return f"偏好已保存: {key} = {value}"
return "存储不可用"五、生命周期上下文
用中间件在模型调用和工具调用之间插入处理逻辑。
5.1 消息摘要
对话太长时自动压缩,节省token:
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="deepseek-v4-flash",
tools=[...],
middleware=[
SummarizationMiddleware(
max_tokens=4000, # 超过4000 token时触发摘要
),
],
)5.2 安全护栏
在工具执行前检查安全性:
from langchain.agents import AgentState
from langchain.agents.middleware import before_model
@before_model
def safety_check(state: AgentState, runtime) -> dict | None:
last_message = state["messages"][-1].content
# 检查是否包含敏感操作
dangerous_keywords = ["DELETE", "DROP", "TRUNCATE"]
if any(keyword in last_message.upper() for keyword in dangerous_keywords):
print("[安全警告] 检测到潜在的危险操作")
return None5.3 审计日志
记录每次模型调用的上下文:
from langchain.agents import AgentState
from langchain.agents.middleware import after_model
@after_model
def audit_log(state: AgentState, runtime) -> dict | None:
user_id = runtime.context.user_id
last_response = state["messages"][-1].content
print(f"[审计] 用户: {user_id}")
print(f"[审计] 模型响应: {last_response[:100]}...")
return None六、瞬态vs持久
一个重要区分:
- 模型上下文是瞬态的:用
wrap_model_call修改的消息、工具、提示词只影响当前这次调用,不会改变State中保存的数据 - 生命周期上下文是持久的:用
before_model、after_model等钩子修改的State会永久保存
# 瞬态:只改当前调用看到的消息,不改State
@wrap_model_call
def transient_change(request, handler):
messages = [*request.messages, {"role": "user", "content": "临时注入"}]
return handler(request.override(messages=messages))
# 持久:修改State,影响后续所有调用
@after_model
def persistent_change(state, runtime):
return {"some_key": "永久保存的值"}七、最佳实践
- 从简单开始:先用静态提示词和固定工具,确认基本功能正常
- 逐步增加动态性:一次只加一个上下文工程特性,测试通过再加下一个
- 监控性能:关注模型调用次数、token消耗、响应延迟
- 用内置中间件:
SummarizationMiddleware、HumanInTheLoopMiddleware等已经实现了常见的上下文管理逻辑 - 文档化你的上下文策略:清楚记录每个Agent看到了什么信息、为什么
八、总结
上下文工程是构建可靠Agent的核心:
- 模型上下文:控制系统提示词、消息、工具、模型、输出格式
- 工具上下文:工具可以从State、Store、Runtime读取数据,也可以写入
- 生命周期上下文:用中间件在步骤之间做摘要、护栏、日志
- 三种数据来源:Runtime Context(静态)、State(短期)、Store(长期)
掌握了上下文工程,你就掌握了让Agent从"能用"变成"好用"的关键。
这是本系列的最后一篇文章。回顾一下我们学过的内容:
- 创建第一个Agent
- 模型组件
- 记忆组件
- Tool组件
- 多轮对话
- Agent状态
- 事件流
- 流式输出
- 结构化输出
- 中间件
- 自定义中间件
- RAG检索
- MCP
- 多Agent
- 人在环路
- Runtime运行时信息
- 上下文工程
从最基础的Agent创建,到最核心的上下文工程,你已经掌握了LangChain v1的完整知识体系。接下来就是动手实践了——选一个你感兴趣的场景,从零开始构建一个Agent,遇到问题再回来查对应的文章。祝你构建出可靠的AI应用!